﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Psm.Util
{
    public sealed class ByteBuffer
    {
        public class BufferState
        {
            public int Position { get; set;}

            public BufferState(ByteBuffer buffer)
            {
                Position = buffer.CurrentIndex;
            }
        }


        private int _startIndex;        
        private byte[] _data;

        /// <summary>
        /// Index of first element or empty location in buffer
        /// </summary>
        public int StartIndex
        {
            get 
            {
                return _startIndex;
            }
        }

        /// <summary>
        /// Index of last element or empty location in buffer
        /// </summary>
        public int EndIndex
        {
            get
            {
                return StartIndex + this.Length - 1;
            }
        }

        /// <summary>
        /// First non occupied index in buffer
        /// </summary>
        public int CurrentIndex { get; private set; }

        /// <summary>
        /// Maximum number of bytes that can be in buffer
        /// </summary>
        public int TotalCapacity { get; private set;}
        
        /// <summary>
        /// Avaialable free space in buffer
        /// </summary>
        public int FreeCapacity 
        {
            get
            {
                return TotalCapacity - Length;
            }
        }

        /// <summary>
        /// Currently occupied length of buffer
        /// </summary>
        public int Length
        {
            get
            {
                return CurrentIndex - _startIndex;
            }
        }

        public ByteBuffer(int totalCapacity = 8192)
        {
            _startIndex = 0;
            _data = new byte[totalCapacity];
            TotalCapacity = _data.Length;
        }

        public ByteBuffer(byte[] bytes, int startIndex, int currentIndex, int totalCapacity)
        {
            _data = bytes;
            _startIndex = startIndex;
            CurrentIndex = currentIndex;
            TotalCapacity = totalCapacity;
        }

        public bool Write(byte data)
        {
            if (FreeCapacity > 0)
            {
                _data[CurrentIndex] = data;
                CurrentIndex = CurrentIndex + 1;
                return true;
            }
            return false;
        }

        public bool Pad(int count)
        {
            if (count <= FreeCapacity)
            {   
                CurrentIndex = CurrentIndex + count;
                return true;
            }
            return false;
        }

        public bool Write(char data)
        {
            if (FreeCapacity > 0)
            {
                _data[CurrentIndex] = (byte)data; 
                CurrentIndex = CurrentIndex + 1;
                return true;
            }
            return false;
        }

        public bool Write(string data)
        {
            if (string.IsNullOrEmpty(data)) return true;
            if (FreeCapacity >= data.Length)
            {
                int byteCounts = ASCIIEncoding.ASCII.GetBytes(data, 0, data.Length, _data, CurrentIndex);
                CurrentIndex = CurrentIndex + byteCounts;
                return true;
            }
            return false;
        }

        public bool WritePrefixed(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                if (FreeCapacity >= 4)
                {
                    Byte[] bytes = BitConverter.GetBytes(0);
                    Buffer.BlockCopy(bytes, 0, _data, CurrentIndex, bytes.Length);
                    CurrentIndex = CurrentIndex + 4;
                    return true;
                }
                else
                    return false;
            } 

            if (FreeCapacity >= data.Length + 4)
            {
                Byte[] bytes = BitConverter.GetBytes(data.Length);
                Buffer.BlockCopy(bytes, 0, _data, CurrentIndex, bytes.Length);
                CurrentIndex = CurrentIndex + 4;

                int byteCounts = ASCIIEncoding.ASCII.GetBytes(data, 0, data.Length, _data, CurrentIndex);
                CurrentIndex = CurrentIndex + byteCounts;
                return true;
            }
            return false;
        }



        public bool Write(int position, int value)
        {
            if (position + 3 < TotalCapacity)
            {
                Byte[] bytes = BitConverter.GetBytes(value);
                Buffer.BlockCopy(bytes, 0, _data, position, bytes.Length);
                return true;
            }
            return false;
        }

        public bool Write(int value)
        {
            if (this.FreeCapacity >= 4)
            {
                Byte[] bytes = BitConverter.GetBytes(value);
                Buffer.BlockCopy(bytes, 0, _data, CurrentIndex, bytes.Length);
                CurrentIndex = CurrentIndex + 4;
                return true;
            }
            return false;
        }

        public bool Write(byte[] data)
        {
            if ( data == null) return true;
            if (FreeCapacity >= data.Length)
            {
                Buffer.BlockCopy(data, 0, _data, CurrentIndex, data.Length);
                CurrentIndex = CurrentIndex + data.Length;
                return true;
            }
            return false;
        }

        public bool Write(ByteBuffer buffer)
        {
            if (buffer == null)
            {
                if (FreeCapacity >= 4)
                {
                    Byte[] bytes = BitConverter.GetBytes(0);
                    Buffer.BlockCopy(bytes, 0, _data, CurrentIndex, bytes.Length);
                    CurrentIndex = CurrentIndex + 4;
                    return true;
                }
                else
                    return false;
            }
            else
            {
                if (FreeCapacity >= buffer.Length + 4)
                {
                    Byte[] bytes = BitConverter.GetBytes(buffer.Length);
                    Buffer.BlockCopy(bytes, 0, _data, CurrentIndex, bytes.Length);
                    CurrentIndex = CurrentIndex + 4;


                    Buffer.BlockCopy(buffer.GetBuffer(), buffer.StartIndex, _data, CurrentIndex, buffer.Length);
                    CurrentIndex = CurrentIndex + buffer.Length;
                    return true;
                }
            }
            return false;
        }

        public byte[] GetBytes()
        {
            byte[] buffer = new byte[this.Length];
            Buffer.BlockCopy(_data, _startIndex, buffer, 0, this.Length);
            return buffer;
        }

        public byte[] GetBuffer()
        {
            return _data;
        }

        public BufferState GetState()
        {
            return new BufferState(this);
        }

        public void RestoreState(BufferState state)
        {
            this.CurrentIndex = state.Position;
        }

        public bool Write(byte[] bytes, int index, int length)
        {
            if (FreeCapacity >= Length)
            {
                Buffer.BlockCopy(bytes, index, _data, CurrentIndex, length);
                CurrentIndex = CurrentIndex + length;
                return true;
            }
            return false;
        }




        public static string ByteBufferToString(ByteBuffer buffer)
        {
            return ASCIIEncoding.ASCII.GetString(buffer.GetBuffer(), buffer.StartIndex, buffer.Length);
        }

        public static ByteBuffer StringToByteBuffer(string _string)
        {
            byte[] bytes = ASCIIEncoding.ASCII.GetBytes(_string);
            return new ByteBuffer(bytes, 0, bytes.Length, bytes.Length);
        }

        public static bool Equals(ByteBuffer left, ByteBuffer right)
        {
            if (left == right) return true; //either same object or both null
            if( left == null || right == null) return false; //one of them is null
            if (left.Length == right.Length)
            {
                byte[] leftBytes = left.GetBuffer();
                byte[] rightBytes = right.GetBuffer();
                for (int index = 0; index < left.Length; index++)
                    if (leftBytes[index + left.StartIndex] != rightBytes[index + right.StartIndex]) return false;
                return true;
            }
            else
                return false;
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int memcmp(byte[] b1, byte[] b2, long count);

        static bool ByteArrayCompare(byte[] b1, byte[] b2)
        {
            // Validate buffers are the same length.
            // This also ensures that the count does not exceed the length of either buffer.  
            return memcmp(b1, b2, b1.Length) == 0;
        }
    }
}
